<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>器灵2D</title>
</head>
<body>

<canvas id="canvasId" width="1000" height="600" style="border: 1px solid gold;"></canvas>
<div>
    <span style="color: rgb(175, 175, 175);">点击屏幕会生成点</span>
    <button id="clearButton">清空</button>
</div>
<canvas id="canvasId1D" width="1000" height="100" style="border: 1px solid gold;"></canvas>


<script>
    //获得 2d 上下文对象
    var canvas = document.getElementById('canvasId');
    var ctx = canvas.getContext('2d');
    // 参数
    var ql = {};                // 方法都绑定到这个上面
    class MyWorld {     // 世界地图
        static cellWidth = 32;         // 每个小格的宽度
        static pointWidth = 32;        // 每个小球的大小
        static 地图宽度 = canvas.width; // 最大宽度
        static 地图高度 = canvas.height; // 最大高度
    }

    //绘制矩形
    ql.画矩形 = function(rgba, x, y){
        ctx.fillStyle = rgba;
        let p = MyWorld.pointWidth / 2;
        ctx.fillRect(x-p, y-p, MyWorld.pointWidth, MyWorld.pointWidth);
    }

    // 画背景
    ql.画背景 = function(){
        ctx.beginPath();
        ctx.strokeStyle = `rgba(0,0,0,0.1)`;
        ctx.lineWidth = 1;
        for (let i = 0; i <= canvas.width; i+=MyWorld.cellWidth) {
            ctx.moveTo(i, 0);
            ctx.lineTo(i, canvas.height);
        }
        for (let i = 0; i <= canvas.height; i+=MyWorld.cellWidth) {
            ctx.moveTo(0, i);
            ctx.lineTo(canvas.width, i);
        }
        ctx.stroke()
    }

    ql.画背景();

    // 一个小点
    class Point {
        static 每格均分多少份 = 32;         // 一格的长度
        static 距离的缩放倍数 = 64;         // 计算力的时候
        static 加速度的缩放倍数 = 1;        // 加速度的影响程度
        static 最大速度 = Math.min(Point.每格均分多少份, 32);   // 速度最多
        static 影响的距离 = 128;            // 只算128距离内的
        static 速度衰减程度 = 0.96;         //  每次刷新速度都会衰减
        static allPoints = [];           // 所有的点
        // 使用 1/x^3 - 1/x^2 困难重重，需要自定义规则来
        constructor(x, y, color, t){
            this.x = x;
            this.y = y;
            this.color = color||`rgba(0,0,0,0.1)`;
            this.t = t||1; // 粒子类型
            this.vx = 0;
            this.vy = 0;
        }
        // 限制范围
        static clamp(v, max) {
            return Math.sign(v) * Math.min(Math.abs(v), max);
        }
        draw() {
            ql.画矩形(this.color, this.x, this.y);
        }
        // 1. 计算速度；加速度由位置来决定
        calc() {
            Point.allPoints.forEach( anoterPoint => {
                let {x:x2, y:y2, vx:vx2, vy:vy2} = anoterPoint;
                let dx = x2 - this.x;
                let dy = y2 - this.y;
                let dxy = Math.sqrt(dx**2 + dy**2);
                if( Math.abs(dxy) > Point.影响的距离 || dxy == 0 ) {
                    return;
                }
                let newDxy = dxy / Point.距离的缩放倍数;
                let axy = dxy===0 ? 0 : Math.sign(newDxy) * ( 1/(Math.abs(newDxy)**3) - 1/(Math.abs(newDxy)**2));
                axy /= Point.加速度的缩放倍数;
                let ax = axy * (dx / Math.abs(dxy));
                let ay = axy * (dy / Math.abs(dxy));
                this.vy -= ay;
                this.vy = Point.clamp(this.vy*Point.速度衰减程度, Point.最大速度);
                this.vx -= ax;
                this.vx = Point.clamp(this.vx*Point.速度衰减程度, Point.最大速度);

                // console.log(`dxy=${dxy}, axy=${axy}, ax=${ax}, ay=${ay}, vx=${this.vx}, vy=${this.vy}`);
            });
        }
        // 2. 按照速度值移动
        move() {
            this.x += this.vx;
            this.y += this.vy;
            this.x = (this.x + MyWorld.地图宽度) % MyWorld.地图宽度;
            this.y = (this.y + MyWorld.地图高度) % MyWorld.地图高度;
        }
    }
    // console.log(Point.aArr);

    ql.画背景();
    const p1 = new Point(150, 50);
    const p2 = new Point(200, 60);
    Point.allPoints.push(p1);
    Point.allPoints.push(p2);

    // // 添加一些点
    for(let i=0; i<30; i++){
        const p = new Point(Math.random()*MyWorld.地图宽度, Math.random()*MyWorld.地图高度);
        Point.allPoints.push(p);
    }

    document.getElementById("clearButton").onclick = e=>Point.allPoints=[];
    // 2022-02-05

    // 点击屏幕加一个点
    canvas.onclick = e => {
        console.info("增加点",e);
        let color = `rgba(${Math.random()*256},${Math.random()*256},${Math.random()*256},${Math.max(Math.random(),0.1)})`;
        Point.allPoints.push(new Point(e.offsetX,e.offsetY,color));
    };

    var intervalId = setInterval(() => {
        ctx.fillStyle = 'rgb(255,255,255)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ql.画背景();
        Point.allPoints.forEach(point => point.calc());
        Point.allPoints.forEach(point => point.move());
        Point.allPoints.forEach(point => point.draw());
        // clearInterval(intervalId);
    }, 10);



// todo 重量，分格子
// todo 参考共价键；链条，刚体，有机的结构
// todo 尝试量化距离，改为查表
// todo 类似于搭积木？


    /**
     * 一维的两个小球
     */
    (function() {
        //获得 1d 上下文对象
        var canvas1 = document.getElementById('canvasId1D');
        var ctx1 = canvas1.getContext('2d');

        function 画矩形(rgba, x, y){
            ctx1.fillStyle = rgba;
            let p = MyWorld.pointWidth / 2;
            ctx1.fillRect(x-p, y-p, MyWorld.pointWidth, MyWorld.pointWidth);
        }

        // 画背景
        画背景 = function(){
            ctx1.beginPath();
            ctx1.strokeStyle = `rgba(0,0,0,0.1)`;
            ctx1.lineWidth = 1;
            for (let i = 0; i <= canvas1.width; i+=MyWorld.cellWidth) {
                ctx1.moveTo(i, 0);
                ctx1.lineTo(i, canvas1.height);
            }
            for (let i = 0; i <= canvas1.height; i+=MyWorld.cellWidth) {
                ctx1.moveTo(0, i);
                ctx1.lineTo(canvas1.width, i);
            }
            ctx1.stroke()
        }

        class Point {
            static 每格均分多少份 = 32;
            static 距离的缩放倍数 = 64;
            static 加速度的缩放倍数 = 1;
            static aArr = new Array(Point.每格均分多少份*5).fill(0).map((_, idx) =>{
                idx -= Point.每格均分多少份*2.5;
                if(idx == 0) {
                    return 0;
                }
                let 符号 = Math.sign(idx);
                idx = Math.abs(idx);
                idx /= Point.距离的缩放倍数;
                return 符号*(1/(idx**3) - 1/(idx**2)) / Point.加速度的缩放倍数;
            });
            // 使用 1/x^3 - 1/x^2 困难重重，需要自定义规则来
            constructor(x, y){
                this.x = x;
                this.y = y;
                this.color = `rgba(0,0,0,0.1)`;
                this.vx = 0;
                this.vy = 0;
                this.anoterPoint = null;
            }
            draw() {
                画矩形(this.color, this.x, this.y);
            }
            // 1. 计算速度；加速度由位置来决定
            calc() {
                let {x:x2, y:y2, vx:vx2, vy:vy2} = this.anoterPoint;
                let dx = x2 - this.x;
                // let ax = Point.aArr[dx + Point.每格均分多少份*2.5];
                let newDx = dx / 40;
                let ax = Math.sign(newDx) * ( 1/(Math.abs(newDx)**3) - 1/(Math.abs(newDx)**2));
                ax *= 1;
                if(!ax) return;
                this.vx -= ax;
                this.vx = Math.sign(this.vx) * Math.min(Math.abs(this.vx), 111111);
                // console.log(`dx=${dx}, ax=${ax}, vx=${this.vx}`);
            }
            // 2. 按照速度值移动
            move() {
                this.x += this.vx;
            }
        }
        // console.log(Point.aArr);

        画背景();
        const p1 = new Point(150, 50);
        const p2 = new Point(200, 50);
        p1.anoterPoint = p2;
        p2.anoterPoint = p1;

        var intervalId = setInterval(() => {
            ctx1.fillStyle = 'rgb(255,255,255)';
            ctx1.fillRect(0, 0, canvas1.width, canvas1.height);
            p1.calc();
            p2.calc();
            p1.move();
            p2.move();
            画背景();
            p1.draw();
            p2.draw();
            // clearInterval(intervalId);
        }, 100);

    })();

</script>

<a href="https://www.runoob.com/w3cnote/html5-canvas-intro.html">canvas 菜鸟教程</a>
</body>
</html>